package com.example.zp.jcenterupload.widget.chart;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Point;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Animation;
import android.view.animation.ScaleAnimation;

import java.util.Random;

/**
 * Created by zp on 2017/11/28.
 */

public class LinearChart extends ViewGroup {
    //    private
    private Paint mAxisPaint;//轴画笔
    private Paint mLinePaint;//拆线画笔
    private Paint mPointPaint;//圆点画笔
    private Paint mAxisXLabelPaint;//X轴标签画笔
    private Paint mAxisYLabelPaint;//Y轴标签画笔
    private Paint mPointSelectPaint;
    private float mProtrudingX;//X轴两边延伸长度
    private float mProtrudingY;//Y轴两边延伸长度
    private float mProtrudingTop;//Y轴顶部延伸长度
    private float mProtrudingLeft;//X轴左部延伸长度
    private float mProtrudingRight;//X轴右部延伸长度
    private float mProtrudingBottom;//Y轴底部延伸长度
    private float mAxisYLabelMargin;//Y轴标签与主图间距
    private float mAxisXLabelMargin;//X轴标签与主图间距
    private float mPointRadius;//圆点半径大小
    private Path mLinePath = new Path();//折线路径
    //允许显示Y轴标签
    private boolean enableYlabel = true;
    //允许显示X轴标签
    private boolean enableXlabel = true;
    //
    private View mPointTip;//提示控件
    //
    private float mClickRadius;//点击半径
    //保存值的数组 长度与X轴标签数一致
    private float[] mValues = new float[0];

    //保存X轴标签数的数组 长度与值的数组长度一致
    private String[] mXLabels = new String[0];
    //Y轴允许的最大值
    private float mMaxValue;
    //Y轴的标签数量（每个标签的值为最大值除以平均值）
    private int mStepNum = 0;
    private Point mSelectPoint;//当前选中的（会显示一个Tip）
    private Point[] mPoint = new Point[0];//保存各个点对应的坐标
    private Point[] mTempAnimatorPoint = new Point[0];//动画缓存

    // 需要在初始化对象的时候进行赋值
    private Label[] mAxisYLabels = new Label[0];//Y轴标签的所有信息
    private Label[] mAxisXLabels = new Label[0];//X轴标签的所有信息

    //X,Y轴表格线
    private Axis[] mAxisY = new Axis[0];//Y轴线段的所有信息
    private Axis[] mAxisX = new Axis[0];//X轴线段的所有信息
    //X轴的步长
    private float mXStep;
    //动画过程的值
    private float mAnimatorValue;//0---1
    //折线图上的点的点击事件
    private OnPointClickListener mListener;

    public LinearChart(Context context) {
        super(context);
        init(context);
    }

    public LinearChart(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    public LinearChart(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }

    @TargetApi(21)
    public LinearChart(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        init(context);
    }

    /**
     * 根据手机的分辨率从 dp 的单位 转成为 px(像素)
     */
    public int dip2px(float dpValue) {
        final float scale = getContext().getResources().getDisplayMetrics().density;
        return (int) (dpValue * scale + 0.5f);
    }


    /**
     * 初始化
     * @param context
     */
    private void init(Context context) {
        setWillNotDraw(false);//继承自ViewGroup需要设置为false否则ondraw不会调用
//        mPointTip = LayoutInflater.from(context).inflate(R.layout.tip_demo, null);
        //初始化画笔
        mAxisPaint = new Paint();
        mAxisPaint.setStyle(Paint.Style.STROKE);//只画路径
        mAxisPaint.setAntiAlias(true);
        mLinePaint = new Paint();
        mLinePaint.setStyle(Paint.Style.STROKE);
        mLinePaint.setStrokeWidth(3);
        mLinePaint.setAntiAlias(true);
        mPointPaint = new Paint();
        mPointPaint.setStyle(Paint.Style.FILL);//填充
        mPointPaint.setAntiAlias(true);
        mAxisXLabelPaint = new Paint();
        mAxisXLabelPaint.setAntiAlias(true);
        mAxisXLabelPaint.setTextSize(dip2px(12));
        mAxisYLabelPaint = new Paint();
        mAxisYLabelPaint.setTextSize(dip2px(12));
        mAxisYLabelPaint.setAntiAlias(true);
        mPointSelectPaint = new Paint();
        mPointSelectPaint.setColor(0xFFdcdcdc);
//      默认值
        mPointRadius = dip2px(3);
        mProtrudingLeft = dip2px(20);
        mProtrudingRight = dip2px(20);
        mProtrudingTop = dip2px(10);
        mAxisYLabelMargin = dip2px(5);
        mClickRadius = dip2px(10);

//        mProtrudingBottom = dip2px(10);

    }

    /**
     * 测试用例
     */
    public void testCase() {
        Random random = new Random();
        for (int i = 0; i < mValues.length; i++) {
            float v = random.nextFloat();
            mValues[i] = v * 10;
        }
        measureAxis();
        postInvalidate();
        playAnimation();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //测量Tip控件的大小
        if (mPointTip != null) {
            int childWidth = getChildMeasureSpec(widthMeasureSpec, 1, LayoutParams.WRAP_CONTENT);
            int childHeight = getChildMeasureSpec(heightMeasureSpec, 1, LayoutParams.WRAP_CONTENT);
            mPointTip.measure(childWidth, childHeight);
        }
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //计算轴以及点的位置
        measureAxis();
    }

    private void measureAxis() {
        int measuredHeight = getMeasuredHeight();//控件高度
        int measuredWidth = getMeasuredWidth();//控件宽度
        //Y轴实际上下左右间距
        float axisYLeft = getPaddingLeft();
        float axisYTop = getPaddingTop();
        float axisYRight = getPaddingRight();
        float axisYBottom = getPaddingBottom();
        //X轴实际上下左右间距
        float axisXLeft = getPaddingLeft();
        float axisXTop = getPaddingTop();
        float axisXRight = getPaddingRight();
        float axisXBottom = getPaddingBottom();
        //Y轴标签的最大长度（用来计算标签所需要占用的横向空间）
        float labelMaxLength = getLabelMaxLength();
        //X,Y轴留出标签的空间
        axisYLeft += labelMaxLength;
        axisXLeft += labelMaxLength;

        float AxisYLabeltextSize = mAxisYLabelPaint.getTextSize();
        //为标签和圆点留出上边距否则标签和圆点有可能会被切割
        if (enableYlabel && AxisYLabeltextSize / 2 > mPointRadius) {
            axisXTop += AxisYLabeltextSize / 2;
            axisYTop += AxisYLabeltextSize / 2;
            axisXBottom += AxisYLabeltextSize / 2;
            axisYBottom += AxisYLabeltextSize / 2;
        } else {
            //如果没有标签的话则只需要考虑圆点的半径
            axisXTop += mPointRadius;
            axisYTop += mPointRadius;
            axisXBottom += mPointRadius;
            axisYBottom += mPointRadius;
        }
        //左右两边也同样留出圆点的空间
        if (mPointRadius > 0) {
            axisXLeft += mPointRadius;
            axisYLeft += mPointRadius;
            axisYRight += mPointRadius;
            axisXRight += mPointRadius;
        }
        //设置X轴凸出两边距离ProtrudingX如果有值则以ProtrudingX优先
        if (mProtrudingX != 0) {
            axisYLeft += mProtrudingX;
            axisYRight += mProtrudingX;
        } else {
            axisYLeft += mProtrudingLeft;
            axisYRight += mProtrudingRight;
        }
        //设置Y轴凸出两边距离ProtrudingY如果有值则以ProtrudingY优先
        if (mProtrudingY != 0) {
            axisXTop += mProtrudingY;
            axisXBottom += mProtrudingY;
        } else {
            axisXTop += mProtrudingTop;
            axisXBottom += mProtrudingBottom;
        }
        //设置轴与Y标签的距离
        if (mAxisYLabelMargin > 0) {
            axisXLeft += mAxisYLabelMargin;
            axisYLeft += mAxisYLabelMargin;
        }
//        设置轴与X标签的距离
        if (mAxisXLabelMargin > 0) {
            axisXBottom += mAxisXLabelMargin;
            axisYBottom += mAxisXLabelMargin;
        }
        //如果允许显示X标签则留出X标签的空间
        if (enableXlabel) {
            float textSize = mAxisXLabelPaint.getTextSize();
            axisXBottom += textSize;
            axisYBottom += textSize;
        }
//        int axisXLeft;
        //X轴的步长等于X轴的有效长度除以值的数组长度减1
        mXStep = (measuredWidth * 1f - axisYLeft - axisYRight) / (mValues.length * 1f - 1);
        //Y轴的每个单位值对应的像素数
        float yValueToPixel = (measuredHeight * 1f - axisXBottom - axisXTop) / mMaxValue;
        //计算X轴标签位置，X轴标签应该与Y轴对齐
        measureAxisXLabel(axisYLeft, 0, measuredWidth - axisXRight, measuredHeight - getPaddingBottom(), mXStep);
        //计算Y轴标签位置，Y轴标签应该与X轴对齐
        float yLabelStep = (measuredHeight * 1f - axisXBottom - axisXTop) / (mStepNum - 1);
        //Y轴标签的底部X轴与Y轴标签字体垂直中心线对齐
        float bottom = measuredHeight + mAxisYLabelPaint.getTextSize() / 2f - axisXBottom;
        measureAxisYLabel(getPaddingLeft(), bottom, labelMaxLength, yLabelStep);
        //计算点的位置
        measurePoint(axisYLeft, measuredHeight - axisXBottom, mXStep, yValueToPixel);
        //设置
        measureAxisX(axisXLeft, measuredHeight - axisXBottom, measuredWidth - axisXLeft - axisXRight, measuredHeight - axisXTop - axisXBottom);
        //
        measureAxisY(axisYLeft, measuredHeight - axisYBottom, measuredHeight - axisYTop - axisYBottom, measuredWidth * 1f - axisYLeft - axisYRight);
    }

    private float getLabelMaxLength() {
        float labelMaxLength = mAxisYLabelPaint.measureText(String.format("%.1f", mMaxValue));//
        for (int i = 0; i < mStepNum; i++) {
            float step = mMaxValue / (mStepNum - 1);
            String strStep = String.format("%.1f", step * i);
            float length = mAxisYLabelPaint.measureText(strStep);
            Label axisYLabel = new Label();
            axisYLabel.setLabel(strStep);
            axisYLabel.setLength(length);
            if (labelMaxLength < length) {
                labelMaxLength = length;
            }
            mAxisYLabels[i] = axisYLabel;
        }
        return labelMaxLength;
    }

    private void measureAxisXLabel(float left, float top, float right, float bottom, float xStep) {
        for (int i = 0; mAxisXLabels != null && i < mAxisXLabels.length; i++) {
            String tempLabel = "xxx";
            if (mXLabels != null && i < mXLabels.length) {
                tempLabel = mXLabels[i];
            }
            Label axisXLabel = new Label();
            float length = mAxisXLabelPaint.measureText(tempLabel);
            int x = (int) (xStep * i + left - length / 2);
            if ((x + length) > right) {
                x = (int) (right - length);
            }
            int y = (int) bottom;
            axisXLabel.setLabel(tempLabel);
            axisXLabel.setLength(length);
            axisXLabel.setX(x);
            axisXLabel.setY(y);
            mAxisXLabels[i] = axisXLabel;
        }
    }

    private void measureAxisYLabel(float left, float bottom, float labelMaxLength, float yLabelStep) {
        for (int i = 0; i < mStepNum; i++) {
            Label mAxisYLabel = mAxisYLabels[i];
            float length = mAxisYLabel.getLength();
            float offset = labelMaxLength - length + left;
            int x = (int) offset;
            int y = (int) (bottom - yLabelStep * i);
            mAxisYLabel.setX(x);
            mAxisYLabel.setY(y);
        }
    }

    private void measurePoint(float left, float bottom, float xStep, float yStep) {
        for (int i = 0; i < mValues.length; i++) {
            int x = (int) (xStep * i + left);
            int y = (int) (bottom - yStep * mValues[i]);
            Point point = new Point();
            point.set(x, y);
            if (mPoint[i] == null) {
                Point tempPoint = new Point();
                tempPoint.set(x, (int) bottom);
                mTempAnimatorPoint[i] = tempPoint;
            } else {
//                mTempAnimatorPoint[i] = mPoint[i];
//                mPoint.clone()
            }
            mPoint[i] = point;


        }
    }

    private void measureAxisX(float startX, float startY, float length, float max) {
        float step = max / (mAxisX.length - 1);
        for (int i = 0; i < mAxisX.length; i++) {
            Axis axisX = new Axis();
            axisX.setStartX(startX);
            axisX.setStartY(startY - step * i);
            axisX.setStopX(startX + length);
            axisX.setStopY(startY - step * i);
            mAxisX[i] = axisX;
        }
    }

    private void measureAxisY(float startX, float startY, float length, float max) {
        float step = max / (mAxisY.length * 1f - 1);
        for (int i = 0; i < mAxisY.length; i++) {
            Axis axisX = new Axis();
            axisX.setStartX(step * i + startX);
            axisX.setStartY(startY);
            axisX.setStopX(step * i + startX);
            axisX.setStopY(startY - length);
            mAxisY[i] = axisX;
        }
    }


    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        if (mSelectPoint == null) {
            playAnimation();
        } else {
            switchTip(mSelectPoint, 200);
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int action = event.getAction();
        switch (action) {
            case MotionEvent.ACTION_UP:
                Log.e("", "");
                break;
            case MotionEvent.ACTION_DOWN:
                float x = event.getX();
                float y = event.getY();
                if (mPoint.length > 0 && mPoint[0] != null) {
                    int x1 = mPoint[0].x;
                    float v = x - x1;
                    int v1 = (int) (v / mXStep);
                    int v2 = v1 + 1;
                    if (mPoint.length > v1 && v1 >= 0) {
                        Point point = mPoint[v1];
                        float v3 = Math.abs(v - mXStep * v1);
                        if (v3 < mClickRadius) {
                            float v4 = Math.abs(point.y - y);
                            double clickDistance = Math.sqrt(v3 * v3 + v4 * v4);
                            if (clickDistance < mClickRadius) {
                                onClick(point, mValues[v1]);
                                return true;
                            }
                        }
                    }
                    if (mPoint.length > v2 && v2 >= 0) {
                        Point point = mPoint[v2];
                        float v3 = Math.abs(v - mXStep * v2);
                        if (v3 < mClickRadius) {
                            float v4 = Math.abs(point.y - y);
                            double clickDistance = Math.sqrt(v3 * v3 + v4 * v4);
                            if (clickDistance < mClickRadius) {
                                onClick(point, mValues[v2]);
                                return true;
                            }
                        }
                    }
                }
                break;
        }

        return super.onTouchEvent(event);
    }

    private void hideTip(long durationMillis) {
        if (mPointTip == null || mPointTip.getTag() == null || (int) mPointTip.getTag() == 1) {
            return;
        }
        ScaleAnimation animationend = new ScaleAnimation(1, 0, 1, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
        animationend.setDuration(durationMillis);
        animationend.setFillAfter(true);
        mPointTip.startAnimation(animationend);
        mPointTip.setTag(1);
    }

    private void showTip(Point point, long durationMillis) {
        if (mPointTip == null) {
            return;
        }
        ScaleAnimation animation = new ScaleAnimation(0, 1, 0, 1, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
        animation.setDuration(durationMillis);
        mPointTip.setAnimation(animation);
        int measuredWidth = mPointTip.getMeasuredWidth();
        int measuredHeight = mPointTip.getMeasuredHeight();
        mPointTip.layout(point.x - measuredWidth / 2, (int) (point.y - measuredHeight - mPointRadius - dip2px(1)), point.x + measuredWidth / 2, (int) (point.y - mPointRadius - dip2px(1)));
        mPointTip.setTag(2);
    }

    private void switchTip(final Point point, final long durationMillis) {
        hideTip(durationMillis);
        postDelayed(new Runnable() {
            @Override
            public void run() {
                showTip(point, durationMillis);
            }
        }, durationMillis);

    }

    private void onClick(final Point point, float value) {
        if (mListener != null) {
            mListener.onPointClick(point, value);
        }
        mSelectPoint = point;
        invalidate();
        requestLayout();
//        if (mSelectPoint == null) {
//            showTip(point, 200);
//        } else {
//            switchTip(point, 200);
//        }
    }

    private void playAnimation() {
        if (mSelectPoint != null) {
            hideTip(100);
            mSelectPoint = null;
        }

        ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, 1);
        valueAnimator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);
                mAnimatorValue = 1f;
                postInvalidate();
                mTempAnimatorPoint = mPoint.clone();
            }
        });
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mAnimatorValue = (float) animation.getAnimatedValue();
                postInvalidate();
            }
        });
        valueAnimator.start();
    }

    private void useAnimatorWithY(Canvas canvas) {
        float animatorValue = mAnimatorValue;
        if (mPoint.length > 0) {
            mLinePath.reset();
            mLinePath.moveTo(mPoint[0].x, mPoint[0].y * animatorValue + mTempAnimatorPoint[0].y * (1f - animatorValue));
        }
        for (int i = 0; i < mPoint.length; i++) {
            mLinePath.lineTo(mPoint[i].x, mPoint[i].y * animatorValue + mTempAnimatorPoint[i].y * (1f - animatorValue));
        }
        drawAxisX(canvas);
        drawAxisY(canvas);
        if (mSelectPoint != null) {
            canvas.drawCircle(mSelectPoint.x, mSelectPoint.y * animatorValue + mSelectPoint.y * (1f - animatorValue), mPointRadius + dip2px(1), mPointSelectPaint);
        }
        canvas.drawPath(mLinePath, mLinePaint);
        for (int i = 0; i < mPoint.length; i++) {
            canvas.drawCircle(mPoint[i].x, mPoint[i].y * animatorValue + mTempAnimatorPoint[i].y * (1f - animatorValue), mPointRadius, mPointPaint);
        }
        for (int i = 0; i < mStepNum; i++) {
            Label mAxisYLabel = mAxisYLabels[i];
            canvas.drawText(mAxisYLabel.getLabel(), mAxisYLabel.getX(), mAxisYLabel.getY(), mAxisYLabelPaint);
        }
        for (int i = 0; i < mAxisXLabels.length; i++) {
            Label mAxisYLabel = mAxisXLabels[i];
            canvas.drawText(mAxisYLabel.getLabel(), mAxisYLabel.getX(), mAxisYLabel.getY(), mAxisXLabelPaint);
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        useAnimatorWithY(canvas);
    }

    private void drawAxisX(Canvas canvas) {
        for (int i = 0; i < mAxisX.length; i++) {
            Axis axisX = this.mAxisX[i];
            canvas.drawLine(axisX.getStartX(), axisX.getStartY(), axisX.getStopX(), axisX.getStopY(), mAxisPaint);
        }
    }

    private void drawAxisY(Canvas canvas) {
        for (int i = 0; i < mAxisY.length; i++) {
            Axis axisX = this.mAxisY[i];
            canvas.drawLine(axisX.getStartX(), axisX.getStartY(), axisX.getStopX(), axisX.getStopY(), mAxisPaint);
        }
    }

    public void setMaxValue(float value) {
        this.mMaxValue = value;
    }

    public void setStepNum(int num) {
        this.mStepNum = Math.max(num, 1);
        this.mAxisYLabels = new Label[mStepNum];
        this.mAxisX = new Axis[mStepNum];
    }

    public void updateValues(float... values) {
        if (values.length != mValues.length) {
            throw new IllegalArgumentException("values.length != mValues.length");
        }
        this.mValues = values.clone();
    }

    public void setValues(float... values) {
        this.mValues = values.clone();
        this.mPoint = new Point[values.length];
        this.mTempAnimatorPoint = new Point[values.length];
        this.mAxisXLabels = new Label[values.length];
        this.mAxisY = new Axis[values.length];
    }

    public void setXLabels(String... labels) {
        this.mXLabels = labels.clone();
    }

    public void setTipView(int layoutId) {
        mPointTip = LayoutInflater.from(getContext()).inflate(layoutId, null);
        addView(mPointTip);
    }

    public void setTipView(View view) {
        mPointTip = view;
        addView(mPointTip);
    }

    public void setAxisYTextSize(float size) {
        mAxisYLabelPaint.setTextSize(dip2px(size));
    }

    public void setAxisXTextSize(float size) {
        mAxisXLabelPaint.setTextSize(dip2px(size));
    }

    public void setAxisYTextColor(int color) {
        mAxisYLabelPaint.setColor(color);
    }

    public void setAxisXTextColor(int color) {
        mAxisXLabelPaint.setColor(color);
    }

    public void setAxisColor(int color) {
        mAxisPaint.setColor(color);
    }

    public void setLineColor(int color) {
        mLinePaint.setColor(color);
    }

    public void setLineWidth(float width) {
        mLinePaint.setStrokeWidth(dip2px(width));
    }

    public void setPointColor(int color) {
        mPointPaint.setColor(color);
    }

    public void setPointRadius(float radius) {
        this.mPointRadius = dip2px(radius);
    }

    public void setProtrudingX(float value) {
        this.mProtrudingX = dip2px(value);
    }

    public void setProtrudingY(float value) {
        this.mProtrudingY = dip2px(value);
    }

    public void setProtrudingLeft(float value) {
        this.mProtrudingLeft = dip2px(value);
    }

    public void setProtrudingTop(float value) {
        this.mProtrudingTop = dip2px(value);
    }

    public void setProtrudingRight(float value) {
        this.mProtrudingRight = dip2px(value);
    }

    public void setProtrudingBottom(float value) {
        this.mProtrudingBottom = dip2px(value);
    }

    public void setAxisYLabelMargin(float margin) {
        this.mAxisYLabelMargin = dip2px(margin);
    }

    public void setAxisXLabelMargin(float margin) {
        this.mAxisXLabelMargin = dip2px(margin);
    }

    public void setEnableYlabel(boolean enable) {
        this.enableYlabel = enable;
    }

    public void setEnableXlabel(boolean enable) {
        this.enableXlabel = enable;
    }

    public void requestDraw() {
        measureAxis();
        postInvalidate();
        playAnimation();
    }

    public void setOnPointClickListener(OnPointClickListener l) {
        this.mListener = l;
    }

    public interface OnPointClickListener {
        void onPointClick(Point point, float value);
    }


}
